/**
 * ECMAScript 6 新增了对象解构语法，可以在一条语句中使用嵌套数据实现一个或多个赋值操作。简单地说，对象解构就是使用与对象匹配的结构来实现对象属性赋值。
 */
// 不使用对象解构
let person = {
    name: 'Matt',
    age: 27
};
// let personName = person.name,
//     personAge = person.age;
// console.log(personName); // Matt
// console.log(personAge); // 27
console.log("======")

// 使用对象解构
// let person = {
//     name: 'Matt',
//     age: 27
// };
// let {name,age} = person
// console.log(name); // Matt
// console.log(age); // 27
console.log("======")

/**
 * 特别重要:
 * 构赋值不一定与对象的属性匹配。赋值的时候可以忽略某些属性，而如果引用的属性不存在，则该变量的值就是 undefined
 */
// let person = {
//     name: 'Matt',
//     age: 27
// };
// let {name,job} = person
// console.log(name);
// console.log(job);
//

/**
 * 特别重要:
 * 可以在解构赋值的同时定义默认值，这适用于前面刚提到的引用的属性不存在于源对象中的
 * 情况
 */
let {name, job = 'Software engineer'} = person
console.log(name);
console.log(job);
console.log("======")

/**
 * 特别重要:
 * 解构在内部使用函数 ToObject()（不能在运行时环境中直接访问）把源数据结构转换为对象。这意味着在对象解构的上下文中，原始值会被当成对象。这也意味着（根据 ToObject()的定义），null 和 undefined 不能被解构，否则会抛出错误。
 */
let {length} = 'foobar'
console.log(length)
let {constructor: c} = 4;
console.log(c === Number); // true

/**
 * 特别重要:
 * 解构并不要求变量必须在解构表达式中声明。不过，如果是给事先声明的变量赋值，则赋值表达式必须包含在一对括号中
 */

let personName, personAge;
// let person = {
//     name: 'Matt',
//     age: 27
// };
({name: personName, age: personAge} = person);
console.log(personName, personAge); // Matt, 27

/**
 * 特别重要:
 * 嵌套解构
 * 解构对于引用嵌套的属性或赋值目标没有限制。为此，可以通过解构来复制对象属性
 */
let person1 = {
    name: 'Matt',
    age: 27,
    job: {
        title: 'Software engineer'
    }
};
let person1Copy = {};
({
    name: person1Copy.name,
    age: person1Copy.age,
    job: person1Copy.job
} = person1);
// 因为一个对象的引用被赋值给 person1Copy，所以修改
// person1.job 对象的属性也会影响 person1Copy
person1.job.title = 'Hacker'
console.log(person1);
// { name: 'Matt', age: 27, job: { title: 'Hacker' } }
console.log(person1Copy);
// { name: 'Matt', age: 27, job: { title: 'Hacker' } }

/**
 * 解构赋值可以使用嵌套结构，以匹配嵌套的属性
 */
let person2 = {
    name: 'Matt',
    age: 27,
    job: {
        title: 'Software engineer'
    }
};
// 声明 title 变量并将 person2.job.title 的值赋给它
let {job: {title}} = person2;
console.log(title); // Software engineer

/**
 * 在外层属性没有定义的情况下不能使用嵌套解构。无论源对象还是目标对象都一样
 */
// let person3 = {
//     job: {
//         title: 'Software engineer'
//     }
// };
// let person3Copy = {};
// // foo 在源对象上是 undefined
// ({
//     foo: {
//         bar: person3Copy.bar
//     }
// } = person3);
// // TypeError: Cannot destructure property 'bar' of 'undefined' or 'null'.
// // job 在目标对象上是 undefined
// ({
//     job: {
//         title: person3Copy.job.title
//     }
// } = person3);
// TypeError: Cannot set property 'title' of undefined

/**
 * 特别重要:
 * 部分解构
 * 需要注意的是，涉及多个属性的解构赋值是一个输出无关的顺序化操作。如果一个解构表达式涉及
 * 多个赋值，开始的赋值成功而后面的赋值出错，则整个解构赋值只会完成一部分
 */

// let person4 = {
//     name: 'Matt',
//     age: 27
// };
// let person4Name, personBar, personAge;
// try {
// // person4.foo 是 undefined，因此会抛出错误
//     ({name: person4Name, foo: { bar: personBar }, age: personAge} = person);
// } catch(e) {}
// console.log(person4Name, personBar, personAge);
// // Matt, undefined, undefined

/**
 * 特别重要:
 * 参数上下文匹配
 * 在函数参数列表中也可以进行解构赋值。对参数的解构赋值不会影响 arguments 对象，但可以在函数签名中声明在函数体内使用局部变量
 */
let person5 = {
    name: 'Matt',
    age: 27
};
function printperson5(foo, {name, age}, bar) {
    console.log("==============")
    console.log(arguments);
    console.log(name, age);
}
function printperson52(foo, {name: person5Name, age: person5Age}, bar) {
    console.log("==============")
    console.log(arguments);
    console.log(person5Name, person5Age);
}
printperson5('1st', person5, '2nd');
// ['1st', { name: 'Matt', age: 27 }, '2nd']
// 'Matt', 27
printperson52('1st', person5, '2nd');
// ['1st', { name: 'Matt', age: 27 }, '2nd']
// 'Matt', 27